/***************************************************
File:           LPK_FadeSoundOnEvent.cs
Authors:        Christopher Onorati
Last Updated:   6/10/2019
Last Version:   2018.3.14

Description:
  This component can be used to fade volume levels on
  AudioSource components.

This script is a basic and generic implementation of its 
functionality. It is designed for educational purposes and 
aimed at helping beginners.

Copyright 2018-2019, DigiPen Institute of Technology
***************************************************/

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace LPK
{

/**
* CLASS NAME  : LPK_FadeSoundOnEvent
* DESCRIPTION : Component used to fade volume levels on audio.
**/
public class LPK_FadeSoundOnEvent : LPK_Component
{
    /************************************************************************************/

    public bool m_bFadeOnStart;

    [Tooltip("Game objects that have audio sources to fade when a valid event is received.  If both this and the tags array are set to length 0, default to self.")]
    public GameObject[] m_FadeSources;

    [Tooltip("Tags to look for that have audio to fade when a valid event is received.  This is only used if the game object array Fade Sources is left empty.  If both this and the tags array are set to length 0, default to self.")]
    [TagDropdown]
    public string[] m_FadeSourceTags;

    public float m_flFadeDuration = 2;
    public float m_flGoalVolume;

    [Header("Event Receiving Info")]

    [Tooltip("Which event will trigger this component's action")]
    public LPK_EventObject m_EventTrigger;

    [Header("Event Sending Info")]

    [Tooltip("Event sent when an audio source finishes fading to a new volume level.")]
    public LPK_EventSendingInfo m_AudioFadeCompletedEvent;

    /************************************************************************************/

    //Initial volume settings when fade was called.
    float[] m_aInitialVolumes;

    float m_flTimer = 0;

    /**
    * FUNCTION NAME: OnStart
    * DESCRIPTION  : Begins event receiving and gathering of fade sources as needed.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    override protected void OnStart()
    {
        if (m_FadeSources.Length == 0)
            FindFadeSources();

        SetInitialLevels();

        if(m_EventTrigger != null)
            m_EventTrigger.Register(this);
    }

    /**
    * FUNCTION NAME: Update
    * DESCRIPTION  : Apply audio fade over time.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void Update()
    {
        if(m_bFadeOnStart)
            FadeAudioLevels();
    }

    /**
    * FUNCTION NAME: OnEvent
    * DESCRIPTION  : Event validation.
    * INPUTS       : _activator - Game object that activated the event.  Null is all objects.
    * OUTPUTS      : None
    **/
    override public void OnEvent(GameObject _activator)
    {
        if(!ShouldRespondToEvent(_activator))
            return;

        if (m_FadeSources == null)
            FindFadeSources();

        SetInitialLevels();

        m_bFadeOnStart = true;
    }

    /**
    * FUNCTION NAME: FindFadeSources
    * DESCRIPTION  : Adds fade sources from tags into the array of objects to fade the volume of.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void FindFadeSources()
    {
        //Default to self.
        if(m_FadeSources.Length == 0 && m_FadeSourceTags.Length == 0)
        {
            if(GetComponent<AudioSource>() == null)
            {
                if (m_bPrintDebug)
                    LPK_PrintDebug(this, "Tried to default fade source to self, but this object does not have an Audio Source component.");
            }
            else
                m_FadeSources = new GameObject[] { gameObject };

            return;
        }

        //Find objects via tag.
        for(int i = 0; i < m_FadeSourceTags.Length;  i++)
        {
            List<GameObject> objs = LPK_MultiTagManager.FindGameObjectsWithTag(gameObject, m_FadeSourceTags[i]);

            for(int j = 0; j < objs.Count; j++)
            {
                //TODOTODO:  This code is ==SUPER== inefficient and should be redone at a later point.  Creating potentially hundreds of new arrays every time this is called is not ideal.
                if(objs[j].GetComponent<AudioSource>() != null)
                {
                    GameObject[] temp = new GameObject[m_FadeSources.Length + 1];

                    for (int k = 0; k < m_FadeSources.Length; k++)
                        temp[k] = m_FadeSources[k];

                    temp[temp.Length - 1] = objs[j];

                    m_FadeSources = temp;
                }
            }
        }

        if (m_FadeSources.Length == 0 && m_bPrintDebug)
            LPK_PrintDebug(this, "No audio sources to fade after tag search!");
    }

    /**
    * FUNCTION NAME: DispatchFadeCompletedEvent
    * DESCRIPTION  : Dispatch event when audio levels have finished fading.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void DispatchFadeCompletedEvent()
    {
        if(m_AudioFadeCompletedEvent != null && m_AudioFadeCompletedEvent.m_Event != null)
        {
            if(m_AudioFadeCompletedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.ALL)
                m_AudioFadeCompletedEvent.m_Event.Dispatch(null);
            else if(m_AudioFadeCompletedEvent.m_EventSendingMode == LPK_EventSendingInfo.LPK_EventSendingMode.OWNER)
                m_AudioFadeCompletedEvent.m_Event.Dispatch(gameObject);

            if (m_bPrintDebug)
                LPK_PrintDebug(this, "Audio Fade Completed event dispatched");
        }
    }

    /**
    * FUNCTION NAME: SetInitialLevels
    * DESCRIPTION  : Set initial audio levels for the fade.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void SetInitialLevels()
    {
        m_aInitialVolumes = new float[m_FadeSources.Length];

        //Gather all of the initial volumes and store them.
        for (int i = 0; i < m_FadeSources.Length; i++)
        {
            if (m_FadeSources[i].GetComponent<AudioSource>())
                m_aInitialVolumes[i] = m_FadeSources[i].GetComponent<AudioSource>().volume;
        }
    }

    /**
    * FUNCTION NAME: FadeAudioLevels
    * DESCRIPTION  : Manages fade of audio overtime.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void FadeAudioLevels()
    {
        if (m_flTimer < m_flFadeDuration)
        {
            m_flTimer += Time.deltaTime;

            for (int i = 0; i < m_FadeSources.Length; i++)
            {
                if (m_FadeSources[i] == null)
                    continue;

                if (m_FadeSources[i].GetComponent<AudioSource>() == null)
                    continue;

                if (m_FadeSources[i].GetComponent<AudioSource>().volume > m_flGoalVolume)
                    m_FadeSources[i].GetComponent<AudioSource>().volume = m_aInitialVolumes[i] - (m_flTimer / m_flFadeDuration);
                else
                    m_FadeSources[i].GetComponent<AudioSource>().volume = m_aInitialVolumes[i] + (m_flTimer / m_flFadeDuration);
            }

            if (m_flTimer <= m_flFadeDuration)
                return;
        }

        //Ensure all audio levels are properly set.
        for (int i = 0; i < m_FadeSources.Length; i++)
        {
            if (m_FadeSources[i] == null)
                continue;

            if (m_FadeSources[i].GetComponent<AudioSource>() == null)
                continue;

            m_FadeSources[i].GetComponent<AudioSource>().volume = m_flGoalVolume;
        }

        m_bFadeOnStart = false;
        m_flTimer = 0;
        DispatchFadeCompletedEvent();
    }

    /**
    * FUNCTION NAME: OnDestroy
    * DESCRIPTION  : Removes game object from the event queue.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void OnDestroy()
    {
        if(m_EventTrigger != null)
            m_EventTrigger.Unregister(this);
    }
}

#if UNITY_EDITOR

[CustomEditor(typeof(LPK_FadeSoundOnEvent))]
public class LPK_FadeSoundOnEventEditor : Editor
{
    SerializedProperty fadeSources;
    SerializedProperty fadeSourcesTags;

    SerializedProperty eventTriggers;

    SerializedProperty m_FadeSoundEventSendingMode;
    SerializedProperty audioFadeCompletedReceivers;

    /**
    * FUNCTION NAME: OnEnable
    * DESCRIPTION  : Save out serialized classes.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    void OnEnable()
    {
        fadeSources = serializedObject.FindProperty("m_FadeSources");
        fadeSourcesTags = serializedObject.FindProperty("m_FadeSourceTags");

        eventTriggers = serializedObject.FindProperty("m_EventTrigger");

        audioFadeCompletedReceivers = serializedObject.FindProperty("m_AudioFadeCompletedEvent");
    }

    /**
    * FUNCTION NAME: OnInspectorGUI
    * DESCRIPTION  : Override GUI for inspector.
    * INPUTS       : None
    * OUTPUTS      : None
    **/
    public override void OnInspectorGUI()
    {
        LPK_FadeSoundOnEvent owner = (LPK_FadeSoundOnEvent)target;

        LPK_FadeSoundOnEvent editorOwner = owner.GetComponent<LPK_FadeSoundOnEvent>();

        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.PrefixLabel("Script");
        editorOwner = (LPK_FadeSoundOnEvent)EditorGUILayout.ObjectField(editorOwner, typeof(LPK_FadeSoundOnEvent), false);
        GUILayout.EndHorizontal();
        EditorGUI.EndDisabledGroup();

        //Undo saving.
        Undo.RecordObject(owner, "Property changes on LPK_FadeSoundOnEvent");

        //Component properties.
        GUILayout.Space(10);
        EditorGUILayout.LabelField("Component Properties", EditorStyles.boldLabel);

        owner.m_bFadeOnStart = EditorGUILayout.Toggle(new GUIContent("Fade on Start", "Flag to set fade to start as soon as this component calls its start function.  This can be used in the insepctor to track when a fade is occuring as well."), owner.m_bFadeOnStart);
        EditorGUILayout.PropertyField(fadeSources, true);
        EditorGUILayout.PropertyField(fadeSourcesTags, true);
        owner.m_flFadeDuration = EditorGUILayout.FloatField(new GUIContent("Fade Duration", "How long for the fade to last."), owner.m_flFadeDuration);
        owner.m_flGoalVolume = EditorGUILayout.FloatField(new GUIContent("Target Volume", "Target volume to fade all sources to."), owner.m_flGoalVolume);

        //Events
        EditorGUILayout.PropertyField(eventTriggers, true);
        EditorGUILayout.PropertyField(audioFadeCompletedReceivers, true);

        //Debug properties.
        GUILayout.Space(10);
        EditorGUILayout.LabelField("Debug Properties", EditorStyles.boldLabel);

        owner.m_bPrintDebug = EditorGUILayout.Toggle(new GUIContent("Print Debug Info", "Toggle console debug messages."), owner.m_bPrintDebug);
        owner.m_sLabel = EditorGUILayout.TextField(new GUIContent("Label", "Notes for the user about this component.  This does nothing to the game or build."), owner.m_sLabel);

        //Apply changes.
        serializedObject.ApplyModifiedProperties();
    }
}

#endif  //UNITY_EDITOR

}   //LPK
